/*
 * Università degli Studi di Trento
 * Facoltà di Scienze MM.FF.NN.
 * Corso di Laurea Triennale in Informatica
 * A.A. 2010/2011
 * Progetto per il corso di Sistemi Operativi 1
 * Progetto 4: simulazione di una rotonda
 * Autori:
 *  Abram Lorenzo - 140368
 *  Dal Lago Loris - 139428
 *  Florio Alessandro - 140245
 */

/********************************************************************************
 Parametri da dare all'eseguibile:
 -h: help
 -f: modalita' da file
 ********************************************************************************/

#include "common.h"
#include "car.h"
#include "priorityQueue.h"
#include "roundabout.h"
#include "camera.h"

#define interactiveMode (inputFD == 0)
#define BUF_SIZE 250
#define NO_MORE_CARS !((buf[0]=='q') && (buf[1]=='u') && (buf[2]=='i') && (buf[3]=='t'))

struct thread_data {
	ptrPQueue PQ;
	ptrBaut baut;
	time_t startTime;
	bool * willQuit;
};

/* Questi mutex servono per sincronizzare il thread checkCarArrival e il thread
 * Camera con il main, che altrimenti terminerebbe troppo presto, distruggendo
 * variabili usate dagli altri thread impedendone la corretta esecuzione  */
static pthread_mutex_t checkCarArrival_mutex;
static pthread_mutex_t camera_mutex;

static void * checkCarArrival(void * arg) {
	struct thread_data * data = (struct thread_data *) arg;

	/* Finche' l'inserimento dell'input non e' terminato controllo la priority queue */
	while (!*data->willQuit) {
		if (!PQueue_isEmpty(data->PQ)) {
			ptrCar topCar = PQueue_top(data->PQ);
			/* Se l'auto in cima alla Priority Queue può partire */
			if (Car_getBautArrivalTime(topCar) <= (long) (time(NULL)
					- data->startTime)) {
				/* Ne effettuo la dequeue e notifico l'evento alla rotatoria
				 * dove verrà genereato il thread Driver */
				topCar = PQueue_dequeue(data->PQ);
				Baut_notify(topCar, data->baut);

#ifdef DEBUG
				printf("AUTO %s DEQUEUED AT TIME: %ld, %s %s\n",
						Car_getTag(topCar), Car_getBautArrivalTime(topCar),
						Car_getStartPointStr(topCar),
						Car_getDestPointStr(topCar));
#endif

			}
		}
	}
	/* "Avviso" il main che ho terminato il mio compito */
	pthread_mutex_unlock(&checkCarArrival_mutex);
	pthread_exit(NULL);
}

static void printUsage() {
	printf("uso: ./baut [-f nomefile] [-h]\n");
}

/* Questa funzione esegue il parsing dell'input e effettua gli inserimenti
 * in priority queue */
static void parseInput(ptrPQueue PQ, int inputFD, time_t startTime) {

	char tag[String_DIM];
	char start[String_DIM];
	char dest[String_DIM];
	char type[String_DIM];
	long arrivalTime;
	int startPlace;
	int destPlace;
	int carType;
	ptrCar car;

	char miniBuf[1];
	char buf[BUF_SIZE];
	int notEofError;

	int b = 0;
	while (((notEofError = read(inputFD, miniBuf, 1)) > 0) && NO_MORE_CARS) {
		// leggo un carattere alla volta fino a fine riga, altrimenti il buffer andrebbe oltre alla riga

		buf[b] = miniBuf[0];

		if (buf[b] == '\n') {

			buf[b + 1] = '\0';

			sscanf(buf, "AUTO %s %s %s %s %ld;\n", tag, start, dest, type,
					&arrivalTime);
			if (interactiveMode)
				arrivalTime += (long) (time(NULL) - startTime);
			startPlace = Car_stringToPlace(start);
			destPlace = Car_stringToPlace(dest);
			carType = Car_stringToType(type);

#ifdef DEBUG
			printf("AUTO %s %s %s %s %ld;", tag, start, dest, type, arrivalTime);
			printf("\nBuffer: %s", buf);
			printf("\nEnd of file? %d", notEofError);
			printf("\nNo more cars? %d", NO_MORE_CARS);
#endif

			if ((startPlace == ERROR_PLACE) || (destPlace == ERROR_PLACE)
					|| (carType == ERROR_TYPOLOGY) || (arrivalTime < 0)) {
				/* assumiamo che in modalita' da file NON ci siano errori di forma */
				printf("Errore di forma nella digitazione.\n");
				printf("\tRiscrivere come: AUTO <targa> <partenza> <destinazione> <tipo> <tempo>;\n\n");
			} else {
				car = Car_create(tag, arrivalTime, startPlace, destPlace, carType);
				PQueue_enqueue(car, PQ);
			}

			b = 0;

		} else
			b++;

	}
	if (interactiveMode) {
		printf("Attendere la terminazione dei thread...\n");
		fflush(stdout);
	}
}

int main(int argc, char* argv[]) {

	time_t startTime = time(NULL);
	/* Inizializzazione della priority queue */
	ptrPQueue PQ = PQueue_create();
	bool willQuit = false;
	bool inputError = false;

	int inputFD = 0;

	if (argc == 1) {
		dup2(0, inputFD);
		if (inputFD == -1)
			inputError = true;
	} else if (argc == 2) {
		if (streq(argv[1],"-h"))
			printUsage();
		else
			inputError = true;
	} else if (argc == 3) {
		if (streq(argv[1], "-f")) {
			printf("\n");
			if ((inputFD = open(argv[2], O_RDONLY)) == -1)
				inputError = true;
		} else
			inputError = true;
	} else {
		inputError = true;
	}

	if (inputError) {
		printf("Errore input non valido: ");
		printUsage();
		return 0;
	}

	/* Creazione del thread camera e inizializzazione delle variabili principali */
	ptrBaut baut = Baut_create();
	ptrCamera camera = Camera_create(baut, interactiveMode, &willQuit,
			&camera_mutex);
	pthread_mutex_init(&checkCarArrival_mutex, NULL);
	pthread_mutex_init(&camera_mutex, NULL);

	struct thread_data * arg = new(struct thread_data);
	arg->PQ = PQ;
	arg->baut = baut;
	arg->startTime = startTime;
	arg->willQuit = &willQuit;

	/* In base alla modalita' che sceglie l'utente faccio partire il thread
	 * checkCarArrival (dopo il parsing dell'input se la modalita' e' da file,
	 * viceversa altrimenti) */
	if (interactiveMode) {
		pthread_t pt;
		int rc = pthread_create(&pt, NULL, checkCarArrival, (void *) arg);
		if (rc)
			throw_error_ends_with_int("main", "return code from pthread_create() is", rc);
		parseInput(PQ, inputFD, startTime);

	} else {
		parseInput(PQ, inputFD, startTime);
		pthread_t pt;
		int rc = pthread_create(&pt, NULL, checkCarArrival, (void *) arg);
		if (rc)
			throw_error_ends_with_int("main", "return code from pthread_create() is", rc);
	}

	if (!interactiveMode) {
		bool fileCloseError = (close(inputFD) == -1);
		if (fileCloseError)
			throw_error_ends_with_str("Main", "Errore nella chiusura del file.", argv[2]);
	}

	/* willQuit: variabile posta ad indicare quando l'inserimento dell'input
	 * e' finito e il programma potra' quindi uscire appena la rotatoria
	 * sara' vuota */
	while (!PQueue_isEmpty(PQ) || !Baut_isEmpty(baut));

	pthread_mutex_lock(&checkCarArrival_mutex);
	pthread_mutex_lock(&camera_mutex);
	willQuit = true;
	/* Attendo che i thread checkCarArrival e camera mi avvisino
	 * che hanno finito */
	pthread_mutex_lock(&checkCarArrival_mutex);
	pthread_mutex_lock(&camera_mutex);

	/* Invocazione distruttori */
	Camera_destroy(camera);
	Baut_destroy(baut);
	PQueue_destroy(PQ);
	pthread_mutex_destroy(&checkCarArrival_mutex);
	pthread_mutex_destroy(&camera_mutex);
	printf("Simulazione Completata!\n");
	pthread_exit(NULL);
}
